﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using YAPL.Util;

namespace YAPL.Language.Core.Types {
	/// <summary>
	/// Base class for all YAPL functions. Every YAPL function
	/// should inherit the following class and override "Call" method.
	/// Functions are stored as objects. Each class has a collection of 
	/// its static and instance functions.
	/// </summary>
	public abstract class YAPLFunction : YAPLObject {
		public const string UNNAMED_PARAMETER = "unnamed";
		public const string SELF_PARAMETER = "_self";
		public const string NAME_PARAMETER = "_name";
		public const string PARSE_SCOPE_METHOD_NAME = "ParseScope";
		public const string CALL_METHOD_NAME = "Call";
		protected ReadOnlyCollection<string> _parameters;
		protected ReadOnlyCollection<YAPLObject> _defaults;
		protected YAPLClass _owner;
		protected string _name;


		protected YAPLFunction(string name, YAPLClass owner) {
			_name = name;
			_owner = owner;
			_owner.Methods[_name] = this;

			//extract ParamAttributes from Call method and fill parameter collection
			ParseParamAttributes();
		}

		private void ParseParamAttributes() {
			var paramAttrs = (ParamAttribute[])GetType().GetMethod(CALL_METHOD_NAME).GetCustomAttributes(typeof(ParamAttribute), true);
			Array.Sort(paramAttrs);
			var paramsList = new List<string>(paramAttrs.Length);
			var defaultsList = new List<YAPLObject>(paramAttrs.Length);
			paramAttrs.Each(param => {
				paramsList.Add(param.ParamName);
				defaultsList.Add(UNDEFINED);
				});

			_parameters = new ReadOnlyCollection<string>(paramsList);
			_defaults = new ReadOnlyCollection<YAPLObject>(defaultsList);
		}

		/// <summary>
		/// basic "Call" method definition. Only sets scope and does
		/// some additional work.
		/// </summary>
		/// <param name="scope">set of variables given to a function</param>
		/// <returns>nothing</returns>
		public abstract YAPLObject Call(Scope scope);

		protected void ParseScope(Scope scope) {
			//set defaults and replace unnamed parameters
			_parameters.Each((paramName, index) => {
				scope[paramName] = _defaults[index];
				scope.ReplaceKey(UNNAMED_PARAMETER + index, paramName);
				});
		}

		public string Name {
			get { return _name; }
		}


		/// <summary>
		/// Determines wether function can be precomputed, if it has constant arguments
		/// </summary>
		/// <returns>true, if function only returns value and does nothing else</returns>
		public virtual bool CanPrecompute {
			get {
				throw new System.NotSupportedException();
			}
		}
	}
}
